#include <QTextStream>
#include <QNetworkProxy>
#include <QSslError>
#include <QtXml>
#include "RequestDescriptor.h"
#include "SrvConnectionHttp.h"
#include "XmlHelper.h"
#include "scImpl.h"

#define FCT "SrvConnectionHttp::SrvConnectionHttp"
SrvConnectionHttp::SrvConnectionHttp(QString serverIp, qint16 serverPort, bool useHttps) :
	SrvConnection(),
	http(serverIp, useHttps?QHttp::ConnectionModeHttps:QHttp::ConnectionModeHttp, serverPort),
	validHeader(false), actTrans(0xffffffff)
{
	ctrc(Trc::SETUP,APP,FCT)<<"serverIp "<<serverIp.toStdString().c_str()<<" serverPort "<<serverPort<<" useHttps "<<useHttps<<endl;
	//ctrc(Trc::SETUP,APP,FCT)<<"state machine: "<<state<<endl;
	connect(&state, SIGNAL(stateChangeSig(int)), this, SLOT(stateChangeSl(int)));
	connect(&http, SIGNAL(authenticationRequired(const QString &, quint16, QAuthenticator *)),
			this, SLOT(httpAuthRequiredSl(const QString &, quint16, QAuthenticator *)));
	connect(&http, SIGNAL(dataReadProgress(int, int)), this, SLOT(httpDataReadProgressSl(int, int)));
	connect(&http, SIGNAL(dataSendProgress(int, int)), this, SLOT(httpDataSendProgressSl(int, int)));
	connect(&http, SIGNAL(done(bool)), this, SLOT(httpDoneSl(bool)));
	connect(&http, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)),
			this, SLOT(httpProxyAuthRequiredSl(const QNetworkProxy &, QAuthenticator *)));
	connect(&http, SIGNAL(readyRead(const QHttpResponseHeader &)), this, SLOT(httpReadyReadSl(const QHttpResponseHeader &)));
	connect(&http, SIGNAL(requestFinished(int, bool)), this, SLOT(httpRequestFinishedSl(int, bool)));
	connect(&http, SIGNAL(requestStarted(int)), this, SLOT(httpRequestStartedSl(int)));
	connect(&http, SIGNAL(responseHeaderReceived(const QHttpResponseHeader &)),
			this, SLOT(httpResponseHeaderReceivedSl(const QHttpResponseHeader &)));
	connect(&http, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(httpSslErrorsSl(const QList<QSslError> &)));
	connect(&http, SIGNAL(stateChanged(int)), this, SLOT(httpStateChangedSl(int)));
}

#undef FCT
#define FCT "SrvConnectionHttp::~SrvConnectionHttp"
SrvConnectionHttp::~SrvConnectionHttp()
{
	ctrc(Trc::SETUP,APP,FCT)<<" deleting "<<requs.size()<<" requests"<<endl;
	emit connectionShutdownSig(this);
	http.disconnect();
	http.abort();
	// we are not the parent but the owner of these objects, so that we can delete them any time we want
	for(Requests::iterator it(requs.begin()); it!=requs.end(); ++it)
		delete it.value();
}

#undef FCT
#define FCT "SrvConnectionHttp::errorString"
QString SrvConnectionHttp::errorString() const
{
	return localError+" "+http.errorString();
}

#undef FCT
#define FCT "SrvConnectionHttp::acceptRequest"
bool SrvConnectionHttp::acceptRequest() const
{
	bool res(state.testTransition(state.ACTIVE())>=0);
	ctrc(Trc::LOOP,APP,FCT)<<"state "<<state.getStateText().c_str()<<": "<<(res?"yes":"no")<<endl;
	return res;
}

#undef FCT
#define FCT "SrvConnectionHttp::acceptTest"
bool SrvConnectionHttp::acceptTest() const
{
	bool res(state.testTransition(state.TEST())>=0);
	ctrc(Trc::LOOP,APP,FCT)<<"state "<<state.getStateText().c_str()<<": "<<(res?"yes":"no")<<endl;
	return res;
}

#undef FCT
#define FCT "SrvConnectionHttp::startTest"
unsigned long SrvConnectionHttp::startTest()
{
	state.makeTransition(state.TEST());
	return createTrans(NULL, 0);
}

#undef FCT
#define FCT "SrvConnectionHttp::startRequest"
unsigned long SrvConnectionHttp::startRequest(RequestDescriptor * requDesc, unsigned long transactionId, bool bootTransaction)
{
	state.makeTransition(state.ACTIVE());
	return createTrans(requDesc, transactionId);
	Q_UNUSED(bootTransaction);
}

#undef FCT
#define FCT "SrvConnectionHttp::getData"
QBuffer * SrvConnectionHttp::getData(unsigned long transId)
{
	Requests::iterator it(requs.find(transId));
	Q_ASSERT(it!=requs.end());
	return it.value()->dBuf;
}

#undef FCT
#define FCT "SrvConnectionHttp::freeData"
void SrvConnectionHttp::freeData(unsigned long transId)
{
	if(transId==0xffffffff)
		return;
	Requests::iterator it(requs.find(transId));
	if(it!=requs.end()) // ignore double free's
	{
		// but be tedious about freeing pending requests
		it.value()->state.makeTransition(it.value()->state.TEARDOWN());
		emit transactionDeleteSig(transId);
		delete it.value();
		requs.erase(it);
	}
}

#undef FCT
#define FCT "SrvConnectionHttp::abortAllTransactions"
void SrvConnectionHttp::abortAllTransactions()
{
	ctrc(Trc::WORK,APP,FCT)<<"state "<<state.getStateText().c_str()<<endl;
	abort();
	r2t.clear();
	for(Requests::iterator it(requs.begin()); it!=requs.end(); ++it)
	{
		StateRequest & sr(it.value()->state);
		if(sr.getState()!=sr.SUCCESS() && sr.getState()!=sr.FAILED() && sr.getState()!=sr.TEARDOWN())
		{
			emit transactionDeleteSig(it.key());
			delete it.value();
			requs.erase(it);
		}
	}
}

#undef FCT
#define FCT "SrvConnectionHttp::createTrans"
unsigned long SrvConnectionHttp::createTrans(RequestDescriptor * requDesc, unsigned long transactionId)
{
	QBuffer * dataBuf(new QBuffer), * paramBuf(0);
	dataBuf->open(QIODevice::ReadWrite);
	int requestId(-1);
	RequestType rt;
	if(requDesc)
	{
		rt=RT_POST;
		paramBuf=requDesc->createParamsBuffer();
		// QString text(paramBuf->data());
		// ctrc(Trc::LOOP,APP,FCT)<<" data "<<text.toStdString().c_str()<<endl;
		requestId=http.post(requDesc->getUrl(false), paramBuf, dataBuf);
		// there is no rt=RT_GET; branch, we use always post...
	}
	else
	{
		rt=RT_HEAD;
		requestId=http.head(RequestDescriptor::getTestUrl());
	}
	 // sensitive to multi-threading
	unsigned long res(transactionId?transactionId:createTransactionId());
	r2t.insert(requestId, res);
	Request * rq(new Request(rt, res, dataBuf, paramBuf));
	requs.insert(res, rq);
	ctrc(Trc::LOOP,APP,FCT)<<" requ "<<requestId<<" -> trans "<<res<<endl;
	connect(&rq->state, SIGNAL(stateSuccessSig(unsigned long)), this, SLOT(requSuccessSl(unsigned long)));
	connect(&rq->state, SIGNAL(stateFailedSig(unsigned long)), this, SLOT(requFailedSl(unsigned long)));
	return res;
}

#undef FCT
#define FCT "SrvConnectionHttp::id2trans"
unsigned long SrvConnectionHttp::id2trans(int id)
{
	Requ2Trans::iterator r2tIt(r2t.find(id));
	if(r2tIt==r2t.end())
	{
		ostringstream os;
		os <<FCT<<": unmapped request ID "<<id;
		throw new AppException(os.str().c_str(), false);
	}
	return r2tIt.value();
}

#undef FCT
#define FCT "SrvConnectionHttp::trans2Rq"
SrvConnectionHttp::Request * SrvConnectionHttp::trans2Rq(unsigned long transId)
{
	Requests::iterator it(requs.find(transId));
	if(it==requs.end())
	{
		ostringstream os;
		os <<transId;
		throw DwkUtil::inst()->createException(APP, EXC_UNKNOWNTRANSACTION, os.str().c_str(), this);
	}
	return it.value();
}

#undef FCT
#define FCT "SrvConnectionHttp::stateChangeSl"
void SrvConnectionHttp::stateChangeSl(int newState)
{
	if(newState==state.FAILED())
		emit connectionAvailableSig(false);
	else if(newState==state.IDLE() || newState==state.ACTIVE())
		emit connectionAvailableSig(true);
}

#undef FCT
#define FCT "SrvConnectionHttp::requSuccessSl"
void SrvConnectionHttp::requSuccessSl(unsigned long transId)
{
	//Request * rq(trans2Rq(transId));
	//rq->dBuf->reset();
	if(state.getState()==state.TEST())
	{
		if(validHeader)
			state.makeTransition(state.IDLE());
		else
			state.makeTransition(state.FAILED());
		emit testReadySig(validHeader, transId);
	}
	else
	{
		state.makeTransition(state.IDLE());
		emit dataReadySig(transId);
	}
}

#undef FCT
#define FCT "SrvConnectionHttp::requFailedSl"
void SrvConnectionHttp::requFailedSl(unsigned long transId)
{
	Requests::iterator it(requs.find(transId));
	if(state.getState()==state.TEST())
	{
		if(localError.isEmpty())
			localError="Invalid transition, maybe no server response.";
		emit testReadySig(false, transId);
	}
	else
		emit requestFailedSig(transId);
	state.makeTransition(state.FAILED());
}

#undef FCT
#define FCT "SrvConnectionHttp::httpAuthRequiredSl"
void SrvConnectionHttp::httpAuthRequiredSl(const QString & hostname, quint16 port, QAuthenticator * auth)
{
	ctrc(Trc::WORK,APP,FCT)<<"state "<<state.getStateText().c_str()<<" auth "<<auth<<endl;
	if(actTrans<0xffffffff)
	{
		Request * rq(trans2Rq(actTrans));
		ctrc(Trc::WORK,APP,FCT)<<" actTrans "<<actTrans<<" '"<<rq->state.getStateText().c_str()<<"'"<<endl;
	}
	if(verb)
	{
		QString text;
		QTextStream texts(&text, QIODevice::WriteOnly);
		texts <<tr("authentication")<<" ("<<hostname<<":"<<port<<")";
		emit infoActionText(text);
	}
}

#undef FCT
#define FCT "SrvConnectionHttp::httpDataReadProgressSl"
void SrvConnectionHttp::httpDataReadProgressSl(int done, int total)
{
	ctrc(Trc::WORK,APP,FCT)<<"state "<<state.getStateText().c_str()<<" done "<<done<<" total "<<total<<endl;
}

#undef FCT
#define FCT "SrvConnectionHttp::httpDataSendProgressSl"
void SrvConnectionHttp::httpDataSendProgressSl(int done, int total)
{
	ctrc(Trc::WORK,APP,FCT)<<"state "<<state.getStateText().c_str()<<" done "<<done<<" total "<<total<<endl;
}

#undef FCT
#define FCT "SrvConnectionHttp::httpDoneSl"
void SrvConnectionHttp::httpDoneSl(bool error)
{
	ctrc(Trc::WORK,APP,FCT)<<"state "<<state.getStateText().c_str()<<" error "<<error<<endl;
	if(actTrans<0xffffffff)
	{
		Request * rq(trans2Rq(actTrans));
		ctrc(Trc::WORK,APP,FCT)<<" actTrans "<<actTrans<<" '"<<rq->state.getStateText().c_str()<<"'"<<endl;
	}
	r2t.clear(); // actually the list should be empty by now
	if(error)
		state.makeTransition(state.FAILED());
	else
	{
		state.tryTransition(state.IDLE()); // no silent clearing of errors
		/*
		if(state.isIdle())
		{
			for(Requests::iterator it(requs.begin()); it!=requs.end(); ++it)
				Q_ASSERT(it.value()->state.isSuccess() || it.value()->state.isFailed());
		}
		*/
	}
}

#undef FCT
#define FCT "SrvConnectionHttp::httpProxyAuthRequiredSl"
void SrvConnectionHttp::httpProxyAuthRequiredSl(const QNetworkProxy & proxy, QAuthenticator * auth)
{
	ctrc(Trc::WORK,APP,FCT)<<"state "<<state.getStateText().c_str()<<" auth "<<auth<<endl;
	if(actTrans<0xffffffff)
	{
		Request * rq(trans2Rq(actTrans));
		ctrc(Trc::WORK,APP,FCT)<<" actTrans "<<actTrans<<" '"<<rq->state.getStateText().c_str()<<"'"<<endl;
	}
	if(verb)
	{
		QString text;
		QTextStream texts(&text, QIODevice::WriteOnly);
		texts <<tr("proxy authentication")<<" ("<<proxy.hostName()<<":"<<proxy.port()<<")";
		emit infoActionText(text);
	}
}

#undef FCT
#define FCT "SrvConnectionHttp::httpReadyReadSl"
void SrvConnectionHttp::httpReadyReadSl(const QHttpResponseHeader & resp)
{
	ctrc(Trc::WORK,APP,FCT)<<"state "<<state.getStateText().c_str()<<endl;
	if(verb)
	{
		QString text;
		QTextStream texts(&text, QIODevice::WriteOnly);
		texts <<tr("response data ready")<<" ("<<resp.reasonPhrase()<<")";
		emit infoActionText(text);
	}
}

#undef FCT
#define FCT "SrvConnectionHttp::httpRequestFinishedSl"
void SrvConnectionHttp::httpRequestFinishedSl(int id, bool error)
{
	ctrc(Trc::WORK,APP,FCT)<<"state "<<state.getStateText().c_str()<<" requ "<<id<<" error "<<error<<endl;
	if(verb)
	{
		QString text;
		QTextStream texts(&text, QIODevice::WriteOnly);
		texts <<tr("request finished")<<" ("<<id<<",";
		if(error)
			texts <<tr("failed");
		else
			texts <<tr("success");
		texts <<")";
		emit infoActionText(text);
	}
	unsigned long transId(id2trans(id));
	Request * rq(0); rq=trans2Rq(transId);
	if(transId!=actTrans)
	{
		ostringstream os;
		os <<FCT<<": strange request ID "<<id<<" -> "<<transId<<" expected "<<actTrans;
		throw new AppException(os.str().c_str(), false);
	}
	r2t.remove(id);
	ctrc(Trc::WORK,APP,FCT)<<" actTrans "<<actTrans<<" from '"<<rq->state.getStateText().c_str()<<"'"<<endl;
	if(error)
		rq->state.makeTransition(rq->state.FAILED());
	else
		rq->state.conditionalTransition(rq->state.SUCCESS());
	actTrans=0xffffffff;
	/* we do not get a signal for receiving body data, so we have to rely on it
		if( (rq->type==RT_HEAD && !rq->dBuf) ||
				(rq->type!=RT_HEAD && rq->dBuf && !rq->dBuf->data().isEmpty()))
			rq->state.makeTransition(rq->state.SUCCESS());
		else
			rq->state.makeTransition(rq->state.FAILED());
	*/
	rq=0;
	try{rq=trans2Rq(transId);}
	catch(AppException * ex)
	{// a signal handler may have deleted the request by now - it's OK
		if(ex->getCauseCode()!=EXC_UNKNOWNTRANSACTION)
			throw ex;
	}
	ctrc(Trc::WORK,APP,FCT)<<" ... to '"<<(rq?rq->state.getStateText().c_str():"request deleted")<<"'"<<endl;
}

#undef FCT
#define FCT "SrvConnectionHttp::httpRequestStartedSl"
void SrvConnectionHttp::httpRequestStartedSl(int id)
{
	ctrc(Trc::WORK,APP,FCT)<<"state "<<state.getStateText().c_str()<<" requ "<<id<<endl;
	if(verb)
		emit infoActionText(QString(tr("request started")));
	actTrans=id2trans(id);
	Request * rq(trans2Rq(actTrans));
	ctrc(Trc::WORK,APP,FCT)<<" actTrans "<<actTrans<<" from '"<<rq->state.getStateText().c_str()<<"'"<<endl;
	rq->state.conditionalTransition(rq->state.SEND());
	ctrc(Trc::WORK,APP,FCT)<<" actTrans "<<actTrans<<" to '"<<rq->state.getStateText().c_str()<<"'"<<endl;
}

#undef FCT
#define FCT "SrvConnectionHttp::httpResponseHeaderReceivedSl"
void SrvConnectionHttp::httpResponseHeaderReceivedSl(const QHttpResponseHeader & resp)
{
	ctrc(Trc::WORK,APP,FCT)<<"state "<<state.getStateText().c_str()<<endl;
	if(verb)
	{
		QString text;
		QTextStream texts(&text, QIODevice::WriteOnly);
		texts <<tr("response header ready")<<" ("<<resp.reasonPhrase()<<")";
		emit infoActionText(text);
	}
	Request * rq(trans2Rq(actTrans));
	ctrc(Trc::WORK,APP,FCT)<<" actTrans "<<actTrans<<" from '"<<rq->state.getStateText().c_str()<<"'"<<endl;
	rq->state.conditionalTransition((rq->state.REC_BODY()));
	ctrc(Trc::WORK,APP,FCT)<<" actTrans "<<actTrans<<" to '"<<rq->state.getStateText().c_str()<<"'"<<endl;
	QStringList prag(resp.value(QString("Pragma")).split(QString(" ")));
	if(prag.size()==4 && prag.at(0)==QString("sybil-app"))
	{
		bool ok1, ok2, ok3;
		versMaj=prag.at(1).toInt(&ok1);
		versMin=prag.at(2).toInt(&ok2);
		versBld=prag.at(3).toInt(&ok3);
		validHeader=ok1 && ok2 && ok3;
	}
	else
	{
		localError="No or invalid Pragma-header.";
		validHeader=false;
	}
	if(rq->type==RT_HEAD)
	{ // copy header data to buffer
		QXmlStreamWriter * wr(XmlHelper::createXmlDoc(rq->dBuf));
		wr->writeStartElement(QString("metaConnAttrs"));
		QStringList keys(resp.keys());
		for(int k(0); k<keys.size(); ++k)
		{
			QStringList allValues(resp.allValues(keys.at(k)));
			for(int v(0); v<allValues.size(); ++v)
			{
				wr->writeEmptyElement(QString("metaConnAttr"));
				wr->writeAttribute(QString("key"), keys.at(k));
				wr->writeAttribute(QString("value"), allValues.at(v));
			}
		}
		wr->writeEndElement(); // metaConnAttrs
		XmlHelper::finishXmlDoc(wr);
	}
}

#undef FCT
#define FCT "SrvConnectionHttp::httpSslErrorsSl"
void SrvConnectionHttp::httpSslErrorsSl(const QList<QSslError> & errors)
{
	ctrc(Trc::WORK,APP,FCT)<<"state "<<state.getStateText().c_str()<<endl;
	if(actTrans<0xffffffff)
	{
		Request * rq(trans2Rq(actTrans));
		ctrc(Trc::WORK,APP,FCT)<<" actTrans "<<actTrans<<" '"<<rq->state.getStateText().c_str()<<"'"<<endl;
	}
	if(verb)
	{
		QString text;
		QTextStream texts(&text, QIODevice::WriteOnly);
		texts <<tr("SSL errors: ");
		for(QList<QSslError>::const_iterator it(errors.begin()); it!=errors.end(); ++it)
			texts <<(*it).errorString()<<"; ";
		emit infoActionText(text);
	}
	state.makeTransition(state.FAILED());
}

#undef FCT
#define FCT "SrvConnectionHttp::httpStateChangedSl"
void SrvConnectionHttp::httpStateChangedSl(int httpState)
{
	QString text;
	bool doTrc(Trc::inst()->doTrace(Trc::LOOP,APP,FCT));
	if(verb || doTrc)
	{
		switch(httpState)
		{
		case 0:
			text="Unconnected"; break;
		case 1:
			text="HostLookup"; break;
		case 2:
			text="Connecting"; break;
		case 3:
			text="Sending"; break;
		case 4:
			text="Reading"; break;
		case 5:
			text="Connected"; break;
		case 6:
			text="Closing"; break;
		default:
			text="?"; break;
		}
	}
	if(doTrc)
	{
		*(Trc::inst())<<Trc::baseInfo(APP,FCT).c_str()<<"'"<<state.getStateText().c_str()<<"' http-state '"<<text.toStdString().c_str()<<"'";
		if(actTrans<0xffffffff)
		{
			Request * rq(trans2Rq(actTrans));
			*(Trc::inst())<<" actTrans "<<actTrans<<" '"<<rq->state.getStateText().c_str()<<"'"<<endl;
		}
		else
			*(Trc::inst())<<endl;
	}
	if(verb)
		emit infoStatusText(text);
	switch(httpState)
	{
	case 4: // Reading
	{
		Request * rq(trans2Rq(actTrans));
		ctrc(Trc::WORK,APP,FCT)<<" actTrans "<<actTrans<<" from '"<<rq->state.getStateText().c_str()<<"'"<<endl;
		rq->state.conditionalTransition(rq->state.REC_HEAD());
		ctrc(Trc::WORK,APP,FCT)<<" actTrans "<<actTrans<<" to '"<<rq->state.getStateText().c_str()<<"'"<<endl;
		if(rq->pBuf)
		{
			delete rq->pBuf;
			rq->pBuf=0;
		}
		break;
	}
	default:
		break;
	}

	// request state is changed through other signals
}
